using System;
using System.Net;
using System.Xml;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Impact.Web.Foundation
{

    /// <summary>
    /// Client interface to Google Reader's API.  Provides methods to access Reader
    /// functionality.  Requires a Google Reader username and password to login to Reader.
    /// </summary>
    public class GoogleReaderClient
    {

        private CookieCollection _Cookies = new CookieCollection();
        private CookieContainer _cookiesContainer = new CookieContainer();


        /// <summary>
        /// constructs a new API object.  
        /// </summary>
        /// <param name="username">the username used to log into Reader</param>
        /// <param name="password">the password used to log into Reader</param>
        public GoogleReaderClient( string username, string password )
        {
            Login( username, password );
        }

        private void Login( string username, string password )
        {
            HttpWebRequest req = CreateRequest( "https://www.google.com/accounts/ServiceLoginAuth" );
            PostLoginForm( req, string.Format( "Email={0}&Passwd={1}&service=reader&continue=https://www.google.com/reader&nui=1", username, password ) );
            string response = GetResponseString( req );

            if ( !response.Contains( "http://www.google.com/reader/atom/user/" ) )
            {
                throw new ApplicationException( string.Format( "Could not login to Google Reader: {0}", response ) );
            }

        }

        /// <summary>
        /// Retrieves a string with current counts of unread items
        /// </summary>
        /// <remarks>
        /// API URL:
        /// https://www.google.com/reader/api/0/unread-count?all=true
        /// </remarks>
        /// <param name="filters">tags to filter by.  can be empty or null.</param>
        /// <returns></returns>
        public string GetDetailedCount( string filters )
        {

            // get the counts from google
            string url = "https://www.google.com/reader/api/0/unread-count?all=true";
            string theXml = GetResponseString( CreateRequest( url ) );

            XmlDocument xdoc = new XmlDocument();
            xdoc.LoadXml( theXml );

            string detailedcount = string.Empty;
            int count = 0;

            // if filters are set, then don't check the regular unread items
            if ( string.IsNullOrEmpty( filters ) )
            {
                count = this.CountAllFeeds( xdoc );
            }
            else
            {
                count = this.CountFilteredFeeds( xdoc, filters, ref detailedcount );
            }

            return string.Format("{0} unread items.{1}{2}", count, Environment.NewLine, detailedcount);
        }

        private int CountAllFeeds( XmlDocument xdoc )
        {
            int totalFeeds = 0;
            foreach ( XmlNode node in xdoc.SelectNodes( "//object/string[contains(.,'feed/http')]" ) )
            {
                int thenumber = Convert.ToInt32( node.ParentNode.SelectSingleNode( "number" ).InnerText );
                totalFeeds += thenumber;
            }
            return totalFeeds;
        }

        private int CountFilteredFeeds( XmlDocument xdoc, string filters, ref string filterBreakdown )
        {
            // filters have been set, check here for just the tagged items.
            string[] filterlist = filters.Split( " ".ToCharArray() );
            int totalFeeds = 0;

            foreach ( XmlNode node in xdoc.SelectNodes( "//object/string[contains(.,'/label/') and contains(.,'user/')]" ) )
            {
                string thelabel = node.InnerText.Substring( node.InnerText.LastIndexOf( "/" ) + 1 );  //user/10477630455154158284/label/food

                foreach ( string thefilter in filterlist )
                {
                    if ( thefilter == thelabel )
                    {
                        int thenumber = Convert.ToInt32( node.ParentNode.SelectSingleNode( "number" ).InnerText );
                        totalFeeds += thenumber;
                        if ( thenumber > 0 )
                        {
                            filterBreakdown += thenumber.ToString() + " in " + thelabel + Environment.NewLine;
                        }
                    }
                }
            }
            return totalFeeds;
        }



        /// <summary>
        /// Retrieves all the feeds currently subscribed for the user
        /// </summary>
        /// <remarks>
        /// API URL:
        /// http://www.google.com/reader/api/0/subscription/list
        /// </remarks>
        /// <returns></returns>
        public Dictionary<string, string> GetSubscriptions()
        {
            return GetDictionary<string, string>( "http://www.google.com/reader/api/0/subscription/list",
                    "string[@name='id']/text()", "string[@name='title']/text()" );
        }

        /// <summary>
        /// Retrieves the number of articles that have not yet been read
        /// </summary>
        /// <returns></returns>
        public Dictionary<string, int> GetUnreadCount()
        {
            return GetDictionary<string, int>( "http://www.google.com/reader/api/0/unread-count?all=true",
                "string[@name='id']/text()", "number[@name='count']/text()" );
        }

        private Dictionary<TKey, TValue> GetDictionary<TKey, TValue>(string url, string keyXPath, string valueXPath)
        {
            string theXml = GetResponseString( CreateRequest( url ) );
            XmlDocument xdoc = new XmlDocument();
            xdoc.LoadXml( theXml );

            Dictionary<TKey, TValue> output = new Dictionary<TKey, TValue>();
            XmlNodeList nodes = xdoc.SelectNodes( "/object/list/object" );
            foreach ( XmlNode node in nodes )
            {
                XmlNode keyNode = node.SelectSingleNode( keyXPath );
                TKey key = (TKey)Convert.ChangeType( keyNode.Value, typeof( TKey ) );
                key = CheckUrl<TKey>( key );

                XmlNode valueNode = node.SelectSingleNode( valueXPath );
                TValue value = (TValue)Convert.ChangeType( valueNode.Value, typeof( TValue ) );
                value = CheckUrl<TValue>( value );

                output.Add( key, value );
            }

            return output;

        }

        /// <summary>
        /// reader appends the phrase "feed/" to each feed URL, so we need to remove it
        /// before we can use it as an actual URL
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        private T CheckUrl<T>( T obj )
        {
            if ( obj is string )
            {
                string url = obj as string;
                if ( url.StartsWith( "feed/" ) )
                {
                    url = url.Replace( "feed/", string.Empty );
                }
                obj = (T)Convert.ChangeType( url, typeof( T ) );
            }
            return obj;
        }


        #region WebRequest Helpers

        private string GetResponseString( HttpWebRequest req )
        {
            string responseString = null;
            try
            {
                HttpWebResponse res = (HttpWebResponse)req.GetResponse();
                res.Cookies = req.CookieContainer.GetCookies( req.RequestUri );
                _Cookies.Add( res.Cookies );

                using ( StreamReader read = new StreamReader( res.GetResponseStream() ) )
                {
                    responseString = read.ReadToEnd();
                    read.Close();
                }
                res.Close();
            }
            catch ( Exception ex )
            {
                string exc = ex.ToString();
            }
            return responseString;
        }

        private void PostLoginForm( HttpWebRequest req, string p )
        {
            req.ContentType = "application/x-www-form-urlencoded";
            req.Method = "POST";

            byte[] b = Encoding.UTF8.GetBytes( p );
            req.ContentLength = b.Length;
            using ( Stream s = req.GetRequestStream() )
            {
                s.Write( b, 0, b.Length );
                s.Close();
            }
        }

        private HttpWebRequest CreateRequest( string url )
        {
            HttpWebRequest req = WebRequest.Create( url ) as HttpWebRequest;
            //req.UserAgent = _user.UserAgent;
            req.CookieContainer = _cookiesContainer;
            //req.Referer = _currentURL; // set the referring url properly to appear as a regular browser
            //_currentURL = url; // set the current url so the next request will have the right referring url ( might not work for sub pages )
            return req;
        }

        #endregion

    }
}
